package ebhack;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.BoxLayout;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import ebhack.SpriteSetsEditor.Sprite;
import ebhack.SpriteSetsEditor.SpriteSet;

public class SpriteEditor extends ToolModule implements ActionListener, DocumentListener {
	private JComboBox selector, palNum;
	private JTextField zoom, width, height;
	private JCheckBoxMenuItem gridLines;
	private SpritePalette spal;
	private IntArrDrawingArea spriteDrawingArea;
	private JScrollPane jsp;

	public SpriteEditor(XMLPreferences prefs) {
		super(prefs);
		reset();
	}
	
	public void reset() {
		
	}

	public String getDescription() {
		return "Sprite Editor";
	}

	public String getVersion() {
		return "0.1";
	}

	public String getCredits() {
		return "Written by MrTenda";
	}

	public void init() {
		mainWindow = createBaseWindow(this);
        mainWindow.setTitle(this.getDescription());
        
        mainWindow.getContentPane().add(
                new JSearchableComboBox(selector = createJComboBoxFromArray(
                    new String[SpriteSetsEditor.getNumSprites()], false), "Sprite: "),
                BorderLayout.NORTH);
        selector.addActionListener(this);
        
        JPanel entry = new JPanel(new BorderLayout());

        JPanel entryEast = new JPanel();
        entryEast.setLayout(new BoxLayout(entryEast, BoxLayout.Y_AXIS));
        
        entryEast.add(getLabeledComponent("Width: ", width = createSizedJTextField(3, true)));
        width.getDocument().addDocumentListener(this);
        entryEast.add(getLabeledComponent("Height: ", height = createSizedJTextField(3, true)));
        height.getDocument().addDocumentListener(this);

        entryEast.add(getLabeledComponent("Palette: ",
            palNum = createJComboBoxFromArray(new Object[8])));
        palNum.addActionListener(this);
        /*tilesetBox = new Box(BoxLayout.Y_AXIS)
        {
            public void setEnabled(boolean b)
            {
                super.setEnabled(b);
                tileset.setEnabled(b);
                tilesetPal.setEnabled(b);
            }
        };
        tilesetBox.add(getLabeledComponent("Tileset: ",
            tileset = createComboBox(TileEditor.TILESET_NAMES, this)));
        tileset.setActionCommand("tilesetSel");
        tilesetBox.add(getLabeledComponent("Tileset Palette:",
            tilesetPal = new JComboBox()));
        tilesetPal.setActionCommand("palNumChanged");
        tilesetPal.addActionListener(this);
        tileset.setSelectedIndex(0);
        entryEast.add(tilesetBox);
        tilesetBox.setEnabled(false);*/
        entryEast.add(ToolModule.pairComponents(getLabeledComponent("Zoom: ",
            pairComponents(zoom = new JTextField(4), new JLabel("%"), true)),
            new JLabel(), false));
        zoom.setText("1000");
        zoom.getDocument().addDocumentListener(this);

        DrawingToolset dt = new DrawingToolset(this);
        entryEast.add(dt);
        entry.add(pairComponents(entryEast, dt, false, true), BorderLayout.EAST);
        
        spal = new SpritePalette(16);
        spal.addActionListener(this);
        JPanel spalWrapper = new JPanel(new FlowLayout());
        spalWrapper.add(spal);
        entry.add(spalWrapper, BorderLayout.SOUTH);
        
        spriteDrawingArea = new IntArrDrawingArea(dt, spal);
        spriteDrawingArea.setZoom(10);
        spriteDrawingArea.setPreferredSize(new Dimension(10 * 32, 10 * 32));
        entry.add(jsp = new JScrollPane(spriteDrawingArea), BorderLayout.CENTER);
        
        mainWindow.getContentPane().add(entry, BorderLayout.CENTER);
        
        // menu
        JMenuBar mb = new JMenuBar();

        JMenu fileMenu = new JMenu("File");
        fileMenu.setMnemonic('f');
        
        fileMenu.add(ToolModule.createJMenuItem("New sprite", 'n', null,
                "newSprite", this));
        fileMenu.add(ToolModule.createJMenuItem("Delete sprite", 'd', null,
                "delSprite", this));
        fileMenu.add(new JSeparator());
        fileMenu.add(ToolModule.createJMenuItem("Import sprite...", 'i', null,
            "importImg", this));
        fileMenu.add(ToolModule.createJMenuItem("Export sprite...", 'e', null,
            "exportImg", this));

        mb.add(fileMenu);

        JMenu editMenu = ToolModule.createEditMenu(this, true);

        editMenu.addSeparator();

        editMenu.add(ToolModule.createJMenuItem("H-Flip", 'h', null, "hFlip",
            this));
        editMenu.add(ToolModule.createJMenuItem("V-Flip", 'v', null, "vFlip",
            this));

        mb.add(editMenu);

        JMenu optionsMenu = new JMenu("Options");

        gridLines = new JCheckBoxMenuItem("Show gridLines");
        gridLines.setMnemonic('g');
        gridLines.setSelected(true);
        gridLines.setActionCommand("gridLines");
        gridLines.addActionListener(this);
        optionsMenu.add(gridLines);

        /*optionsMenu.add(ToolModule.createJMenuItem("Set background color", 'b',
            null, "setBgColor", this));*/

        optionsMenu.add(ToolModule.createJMenuItem("Edit Sprite Set", 'g',
            null, "jumpSet", this));

        mb.add(optionsMenu);

        mainWindow.setJMenuBar(mb);

        /*addDataListener(sptNames, new ListDataListener()
        {

            public void contentsChanged(ListDataEvent lde)
            {
                initSelector(showRepeats.isSelected());
            }

            public void intervalAdded(ListDataEvent arg0)
            {}

            public void intervalRemoved(ListDataEvent arg0)
            {}
        });*/
        
        mainWindow.invalidate();
        mainWindow.pack();
        mainWindow.setLocationByPlatform(true);
        mainWindow.validate();
        //mainWindow.setResizable(false);
	}
	
	public void show() {
		super.show();
		selector.setSelectedIndex(Math.max(selector.getSelectedIndex(), 0));
		mainWindow.setVisible(true);
	}
	
	public void show(Object in) {
		super.show(in);
		if (in instanceof Integer) {
			selector.setSelectedIndex(((Integer) in).intValue());
			
			mainWindow.setVisible(true);
		} else {
			throw new IllegalArgumentException();
		}
	}

	public void load(Project proj) { }

	public void save(Project proj) { }

	public void hide() {
		if (isInited)
			mainWindow.setVisible(false);
	}
	
	void applyChanges(Sprite sp) {
		// Save sprite image
		int newW = Integer.parseInt(width.getText());
		int newH = Integer.parseInt(height.getText());
		if ((sp.getWidth() != newW) || (sp.getHeight() != newH)) {
			if (SpriteSetsEditor.getSpriteSet(sp) != null) {
				JOptionPane.showMessageDialog(mainWindow,
	                    "Can't resize a sprite while it's in use by a sprite set.\n"
						+ "Please remove the sprite from any sprite sets, then save.",
	                    "Could Not Apply Changes", JOptionPane.ERROR_MESSAGE);
				return;
			} else {
				sp.resize(newW, newH);
			}
		}
		sp.setImage(spriteDrawingArea.getIntArrImage());
		
		// Save the palette
		if (palNum.getSelectedIndex() != 4) {
			SpriteSetsEditor.getPalette(palNum.getSelectedIndex()).setColors(spal.getPalette());
		}
	}
	
	void showInfo(Sprite sp) {
		SpriteSet ss = SpriteSetsEditor.getSpriteSet(sp);
		palNum.removeActionListener(this);
		if (ss == null) {
			palNum.setSelectedIndex(0);
			spal.setPalette(SpriteSetsEditor.getPalette(0).getColorPalette());
		} else {
			palNum.setSelectedIndex(ss.getPalette());
			spal.setPalette(SpriteSetsEditor.getPalette(ss.getPalette()).getColorPalette());
		}
		palNum.addActionListener(this);
		
		width.getDocument().removeDocumentListener(this);
		width.setText(Integer.toString(sp.getWidth()));
		width.getDocument().addDocumentListener(this);
		height.getDocument().removeDocumentListener(this);
		height.setText(Integer.toString(sp.getHeight()));
		height.getDocument().addDocumentListener(this);
	}
	
	void updateGUI(Sprite sp) {
		spal.setPalette(SpriteSetsEditor.getPalette(palNum.getSelectedIndex()).getColorPalette());
		spal.repaint();
		spriteDrawingArea.setImage(sp.getImage());
		jsp.updateUI();
	}
	
    public void importImg(File f)
    {
        if (f == null)
            return;
        try
        {
            if (!f.exists())
                throw new FileNotFoundException();
            //Sprite sp = new Sprite(getSpriteInfo(HackModule.getNumberOfString(
            //    selector.getSelectedItem().toString(), false)), this);
            int[][] image = spriteDrawingArea.getIntArrImage();
            try
            {
            	convertImage(ImageIO.read(f), image, spal.getPalette());
                this.spriteDrawingArea.setImage(image);
                this.spriteDrawingArea.repaint();
            }
            catch (ArrayIndexOutOfBoundsException e)
            {
                JOptionPane.showMessageDialog(mainWindow,
                    "Image import failed due to an image of invalid size.\n"
                        + "Try checking the size of the image "
                        + "and changing the size\n"
                        + "of the target sprite if necessary.",
                    "Error: Import Failed", JOptionPane.ERROR_MESSAGE);
            }
        }
        catch (IOException e)
        {
            System.out.println("Unable to read image: " + f.toString());
            e.printStackTrace();
        }
    }

    public void importImg()
    {
    	importImg(chooseFile(true, "png", "Portable Network Graphics", null, null, "Choose an Image to Import"));
    }

    public void exportImg(File f)
    {
        if (f == null)
            return;
        if (!f.getAbsolutePath().endsWith(".png"))
        {
            f = new File(f.getAbsolutePath() + ".png");
        }
        try
        {
            ImageIO.write((BufferedImage) spriteDrawingArea.getImage(), "png", f);
        }
        catch (IOException e)
        {
            System.out.println("Unable to read image: " + f.toString());
            e.printStackTrace();
        }
    }

    public void exportImg()
    {
        exportImg(chooseFile(true, "png", "Portable Network Graphics", null, null, "Choose an Image to Export"));
    }
	
	public void actionPerformed(ActionEvent e) {
		if (e.getSource().equals(selector)) {
			Sprite sp = SpriteSetsEditor.getSprite(Integer.toString(selector.getSelectedIndex()));
			showInfo(sp);
			updateGUI(sp);
		} else if (e.getSource().equals(palNum)) {
			Sprite sp = SpriteSetsEditor.getSprite(Integer.toString(selector.getSelectedIndex()));
			updateGUI(sp);
		} else if (e.getSource().equals(gridLines)) {
			spriteDrawingArea.setDrawGridlines(gridLines.isSelected());
            spriteDrawingArea.repaint();
		} else if (e.getSource().equals(spal)) {
			// A color changed
			spal.repaint();
            spriteDrawingArea.repaint();
		} else if (e.getActionCommand().equals("hFlip"))
        {
            spriteDrawingArea.doHFlip();
        }
        else if (e.getActionCommand().equals("vFlip"))
        {
            spriteDrawingArea.doVFlip();
        }
        else if (e.getActionCommand().equals("undo"))
        {
            spriteDrawingArea.undo();
            spriteDrawingArea.repaint();
        }
        else if (e.getActionCommand().equals("cut"))
        {
            spriteDrawingArea.cut();
            spriteDrawingArea.repaint();
        }
        else if (e.getActionCommand().equals("copy"))
        {
            spriteDrawingArea.copy();
            spriteDrawingArea.repaint();
        }
        else if (e.getActionCommand().equals("paste"))
        {
            spriteDrawingArea.paste();
            spriteDrawingArea.repaint();
        }
        else if (e.getActionCommand().equals("delete"))
        {
            spriteDrawingArea.delete();
            spriteDrawingArea.repaint();
        } else if (e.getActionCommand().equals("apply")) {
			Sprite sp = SpriteSetsEditor.getSprite(Integer.toString(selector.getSelectedIndex()));
        	applyChanges(sp);
        } else if (e.getActionCommand().equals("close")) {
            hide();
        } else if (e.getActionCommand().equals("jumpSet")) {
        	Ebhack.main.showModule(SpriteSetsEditor.class,
        			SpriteSetsEditor.getSprite(Integer.toString(selector.getSelectedIndex())));
        } else if (e.getActionCommand().equals("newSprite")) {
        	Sprite sp = SpriteSetsEditor.newSprite();
        	selector.removeActionListener(this);
        	selector.addItem(getNumberedString("", Integer.parseInt(sp.getId()), false));
        	selector.addActionListener(this);
        	selector.setSelectedIndex(Integer.parseInt(sp.getId()));
        } else if (e.getActionCommand().equals("delSprite")) {
        	Sprite sp = SpriteSetsEditor.getSprite(Integer.toString(selector.getSelectedIndex()));
        	SpriteSet ss = SpriteSetsEditor.getSpriteSet(sp);
        	if (ss != null) {
				JOptionPane.showMessageDialog(mainWindow,
	                    "Can't delete a sprite while it's in use by a sprite set.\n"
						+ "Please remove the sprite from any sprite sets, then save.",
	                    "Could Not Delete Sprite", JOptionPane.ERROR_MESSAGE);
        	} else if (SpriteSetsEditor.getNumSprites() == 1) {
        		JOptionPane.showMessageDialog(mainWindow,
	                    "Can't delete the only sprite.",
	                    "Could Not Delete Sprite", JOptionPane.ERROR_MESSAGE);
        	} else {
        		SpriteSetsEditor.deleteSprite(sp);
        		selector.setSelectedIndex(Math.max(0, selector.getSelectedIndex()-1));
    			selector.removeActionListener(this);
    			selector.removeItemAt(SpriteSetsEditor.getNumSprites());
    			selector.addActionListener(this);
        	}
        } else if (e.getActionCommand().equals("importImg")) {
        	importImg();
        } else if (e.getActionCommand().equals("exportImg")) {
        	exportImg();
        }
	}
	
    private void updateZoom()
    {
        try
        {
            this.spriteDrawingArea
                .setZoom(Float.parseFloat(zoom.getText()) / 100);
            jsp.updateUI();
        }
        catch (NumberFormatException nfe)
        {}
    }
    
    // Simply crops an image or adds extra background
    public int[][] resizeImage(int w, int h, int[][] oldImage) {
    	int[][] newImage = new int[w*8][h*8];
    	for (int i = 0; i < w*8; i++) {
    		for (int j = 0; j < h*8; j++) {
    			if ((i >= oldImage.length) || (j >= oldImage[0].length))
    				newImage[i][j] = 0;
    			else
    				newImage[i][j] = oldImage[i][j];
    		}
    	}
    	return newImage;
    }
	
	public void changedUpdate(DocumentEvent e) {
		int newW, newH;
		if ((e.getDocument().equals(width.getDocument()) || e.getDocument().equals(height.getDocument()))
				&& (width.getText().length() > 0) && (height.getText().length() > 0)
				&& ((newW = Integer.parseInt(width.getText())) > 0)
				&& ((newH = Integer.parseInt(height.getText())) > 0)) {
			int[][] newImage = resizeImage(
					newW, newH,
					spriteDrawingArea.getIntArrImage());
			spriteDrawingArea.setImage(newImage);
			jsp.updateUI();
		} else if (e.getDocument().equals(zoom.getDocument()))
			updateZoom();
	}

	public void insertUpdate(DocumentEvent e) {
		changedUpdate(e);
	}

	public void removeUpdate(DocumentEvent e) {
		changedUpdate(e);
	}
}